import { Platform, PermissionsAndroid } from 'react-native';
import AudioRecord from 'react-native-audio-record';
import CryptoJS from 'crypto-js';
import { PERMISSIONS, check, RESULTS } from 'react-native-permissions';
import { IFLYTEK_APP_ID, IFLYTEK_API_KEY, IFLYTEK_API_SECRET } from '@env';

const BASE_WS_URL = 'wss://office-api-ast-dx.iflyaisol.com/ast/communicate/v1';
const AUDIO_FRAME_SIZE = 1280; // 40ms * 16kHz * 16bit / 8
const FRAME_INTERVAL_MS = 40;
const BASE64_CHARS =
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

const padNumber = value => value.toString().padStart(2, '0');

const formatUtc = () => {
  const now = new Date();
  const offsetMinutes = -now.getTimezoneOffset();
  const hours = Math.floor(offsetMinutes / 60);
  const minutes = offsetMinutes % 60;
  const sign = hours >= 0 ? '+' : '-';

  return (
    `${now.getFullYear()}-${padNumber(now.getMonth() + 1)}-${padNumber(
      now.getDate(),
    )}` +
    `T${padNumber(now.getHours())}:${padNumber(now.getMinutes())}:${padNumber(
      now.getSeconds(),
    )}` +
    `${sign}${padNumber(Math.abs(hours))}${padNumber(Math.abs(minutes))}`
  );
};

const base64ToUint8Array = input => {
  if (!input) {
    return new Uint8Array(0);
  }

  let str = input.replace(/[^A-Za-z0-9+/=]/g, '');
  let i = 0;
  const output = [];

  while (i < str.length) {
    const enc1 = BASE64_CHARS.indexOf(str.charAt(i++));
    const enc2 = BASE64_CHARS.indexOf(str.charAt(i++));
    const enc3 = BASE64_CHARS.indexOf(str.charAt(i++));
    const enc4 = BASE64_CHARS.indexOf(str.charAt(i++));

    const chr1 = (enc1 << 2) | (enc2 >> 4);
    const chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
    const chr3 = ((enc3 & 3) << 6) | enc4;

    output.push(chr1 & 0xff);

    if (enc3 !== 64 && enc3 !== -1) {
      output.push(chr2 & 0xff);
    }

    if (enc4 !== 64 && enc4 !== -1) {
      output.push(chr3 & 0xff);
    }
  }

  return Uint8Array.from(output);
};

const filteredSortedParams = params => {
  const entries = Object.entries(params).filter(
    ([, value]) => value != null && `${value}`.trim() !== '',
  );
  return entries.sort(([a], [b]) => (a > b ? 1 : -1));
};

const ensureSecret = () => {
  if (!IFLYTEK_API_SECRET) {
    throw new Error('IFLYTEK_API_SECRET 未配置，无法生成讯飞鉴权签名');
  }
  return IFLYTEK_API_SECRET;
};

const generateSignature = params => {
  const sortedEntries = filteredSortedParams(params);
  const baseString = sortedEntries
    .map(
      ([key, value]) =>
        `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
    )
    .join('&');

  const secret = ensureSecret();
  const hash = CryptoJS.HmacSHA1(baseString, secret);
  return CryptoJS.enc.Base64.stringify(hash);
};

const buildWsUrl = (extraParams = {}) => {
  const params = {
    appId: IFLYTEK_APP_ID,
    accessKeyId: IFLYTEK_API_KEY,
    uuid: `${Platform.OS}_${Date.now()}`,
    utc: formatUtc(),
    audio_encode: 'pcm_s16le',
    lang: 'autodialect',
    samplerate: '16000',
    ...extraParams,
  };

  params.signature = generateSignature(params);

  const query = filteredSortedParams(params)
    .map(
      ([key, value]) =>
        `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
    )
    .join('&');

  return `${BASE_WS_URL}?${query}`;
};

class IFlytekRealtimeAsrService {
  constructor() {
    this.websocket = null;
    this.sendTimer = null;
    this.captureActive = false;
    this.pendingBytes = new Uint8Array(0);
    this.chunkQueue = [];
    this.sessionId = null;
    this.listeners = {};
    this.manualStop = false;
    this.audioInitialized = false;
    this.audioDataListener = null;
  }

  async startListening({ onPartial, onFinal, onError, onClose, params } = {}) {
    if (this.websocket) {
      throw new Error('已有实时识别任务正在运行');
    }

    this.listeners = { onPartial, onFinal, onError, onClose };
    this.manualStop = false;
    this.sessionId = null;
    this.pendingBytes = new Uint8Array(0);
    this.chunkQueue = [];

    try {
      await this.ensurePermission();
      this.initAudioCapture();
      await this.connectWebSocket(params);
    } catch (error) {
      this.handleError(error);
      throw error;
    }
  }

  async stopListening() {
    this.manualStop = true;
    await this.stopAudioCapture();
    this.flushRemaining();
    this.stopSender();
    this.sendEndFrame();
    this.closeSocket();
    this.listeners = {};
  }

  async ensurePermission() {
    // 仅检查权限，不再请求（权限已在应用启动时统一请求）
    if (Platform.OS === 'android') {
      const granted = await PermissionsAndroid.check(
        PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
      );

      if (!granted) {
        throw new Error(
          '需要麦克风权限才能进行语音识别，请在应用设置中允许访问麦克风',
        );
      }
    } else if (Platform.OS === 'ios') {
      // iOS 检查麦克风和语音识别权限
      const microphoneStatus = await check(PERMISSIONS.IOS.MICROPHONE);

      if (microphoneStatus !== RESULTS.GRANTED) {
        throw new Error(
          '需要麦克风权限才能进行语音识别，请在应用设置中允许访问麦克风',
        );
      }

      // 检查语音识别权限
      const speechStatus = await check(PERMISSIONS.IOS.SPEECH_RECOGNITION);

      if (speechStatus !== RESULTS.GRANTED) {
        throw new Error('需要语音识别权限，请在应用设置中允许使用语音识别功能');
      }
    }
  }

  initAudioCapture() {
    if (!this.audioInitialized) {
      AudioRecord.init({
        sampleRate: 16000,
        channels: 1,
        bitsPerSample: 16,
        audioSource: 6,
        wavFile: 'iflytek_temp.wav',
      });
      this.audioInitialized = true;
    }

    this.captureActive = true;

    // 移除旧的监听器（如果存在）
    if (this.audioDataListener) {
      AudioRecord.removeListener('data', this.audioDataListener);
    }

    // 创建并保存监听器引用
    this.audioDataListener = data => {
      if (!this.captureActive || !data) {
        return;
      }
      const bytes = base64ToUint8Array(data);
      if (!bytes || !bytes.length) {
        return;
      }
      this.appendBytes(bytes);
    };

    AudioRecord.on('data', this.audioDataListener);
  }

  async stopAudioCapture() {
    this.captureActive = false;

    // 清理监听器
    if (this.audioDataListener) {
      try {
        AudioRecord.removeListener('data', this.audioDataListener);
      } catch (error) {
        // 忽略移除监听器时的错误
      }
      this.audioDataListener = null;
    }

    try {
      await AudioRecord.stop();
    } catch {
      // 忽略停止录音时的异常，通常是因为尚未开始录音
    }
  }

  async connectWebSocket(params) {
    return new Promise((resolve, reject) => {
      try {
        const url = buildWsUrl(params);
        this.websocket = new WebSocket(url);
        this.websocket.binaryType = 'arraybuffer';

        this.websocket.onopen = async () => {
          try {
            this.startSender();
            await AudioRecord.start();
            resolve();
          } catch (error) {
            reject(error);
          }
        };

        this.websocket.onmessage = event => {
          this.handleMessage(event.data);
        };

        this.websocket.onerror = event => {
          const message = event?.message || '实时语音识别连接错误';
          this.handleError(new Error(message));
        };

        this.websocket.onclose = () => {
          this.stopSender();
          if (!this.manualStop && this.listeners.onError) {
            this.listeners.onError(new Error('实时语音识别连接已关闭'));
          }
          if (this.listeners.onClose) {
            this.listeners.onClose();
          }
          this.websocket = null;
        };
      } catch (error) {
        reject(error);
      }
    });
  }

  handleMessage(message) {
    if (!message) {
      return;
    }

    try {
      const payload =
        typeof message === 'string' ? JSON.parse(message) : message;

      if (payload.msg_type === 'action' && payload.data?.sessionId) {
        this.sessionId = payload.data.sessionId;
        return;
      }

      if (payload.msg_type === 'result' && payload.data?.cn?.st?.rt) {
        const words = payload.data.cn.st.rt
          .flatMap(item => item.ws || [])
          .flatMap(ws => ws.cw || [])
          .map(cw => cw.w || '')
          .join('');

        if (payload.data.ls) {
          if (this.listeners.onFinal) {
            this.listeners.onFinal(words);
          }
        } else if (this.listeners.onPartial) {
          this.listeners.onPartial(words);
        }
      }
    } catch (error) {}
  }

  handleError(error) {
    if (this.listeners.onError) {
      const err = error instanceof Error ? error : new Error(String(error));
      this.listeners.onError(err);
    }
    this.stopListening();
  }

  appendBytes(newBytes) {
    if (!newBytes || !newBytes.length) {
      return;
    }

    const combined = new Uint8Array(this.pendingBytes.length + newBytes.length);
    combined.set(this.pendingBytes, 0);
    combined.set(newBytes, this.pendingBytes.length);
    this.pendingBytes = combined;

    while (this.pendingBytes.length >= AUDIO_FRAME_SIZE) {
      const chunk = this.pendingBytes.slice(0, AUDIO_FRAME_SIZE);
      this.pendingBytes = this.pendingBytes.slice(AUDIO_FRAME_SIZE);
      this.chunkQueue.push(chunk);
    }
  }

  flushRemaining() {
    if (this.pendingBytes.length > 0) {
      this.chunkQueue.push(this.pendingBytes);
      this.pendingBytes = new Uint8Array(0);
    }
  }

  startSender() {
    if (this.sendTimer) {
      clearInterval(this.sendTimer);
    }

    this.sendTimer = setInterval(() => {
      if (!this.websocket || this.websocket.readyState !== 1) {
        return;
      }

      if (!this.chunkQueue.length) {
        return;
      }

      const chunk = this.chunkQueue.shift();
      if (!chunk) {
        return;
      }

      try {
        const buffer = chunk.buffer.slice(
          chunk.byteOffset,
          chunk.byteOffset + chunk.byteLength,
        );
        this.websocket.send(buffer);
      } catch (error) {
        this.handleError(error);
      }
    }, FRAME_INTERVAL_MS);
  }

  stopSender() {
    if (this.sendTimer) {
      clearInterval(this.sendTimer);
      this.sendTimer = null;
    }
    this.chunkQueue = [];
  }

  sendEndFrame() {
    if (!this.websocket || this.websocket.readyState !== 1) {
      return;
    }

    const payload = { end: true };
    if (this.sessionId) {
      payload.sessionId = this.sessionId;
    }

    try {
      this.websocket.send(JSON.stringify(payload));
    } catch (error) {}
  }

  closeSocket() {
    if (this.websocket) {
      try {
        if (
          this.websocket.readyState === 1 ||
          this.websocket.readyState === 0
        ) {
          this.websocket.close(1000, 'client stop');
        }
      } catch (error) {}
      this.websocket = null;
    }
  }
}

const iflytekRealtimeAsrService = new IFlytekRealtimeAsrService();

export default iflytekRealtimeAsrService;
